﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using System.Diagnostics;

namespace ModelViewer
{
    public partial class AdjustControls : UserControl, TreeViewer.IObserver
    {
        /// <summary>
        /// The tree viewer this control is associated with
        /// </summary>
        private TreeViewer treeViewer = null;

        /// <summary>
        /// The associated tree viewer
        /// </summary>
        [Browsable(false)]
        public TreeViewer TreeViewer
        {
            get { return treeViewer; }
            set
            {
                treeViewer = value;
                if (treeViewer != null)
                    treeViewer.AddObserver(this);
                UpdateUI();
            }
        }

        Timer timer = new Timer();
        private Stopwatch stopwatch = new Stopwatch();
        private long lastTime = 0;

        public const double JogRotateRange = 15;    // +- 15 degrees
        public const double JogTranRange = 10;      // += 10 cm
        public const double JogScaleRange = 0.20;   // +- 20%

        private IPlaceable Placeable
        {
            get
            {
                if (treeViewer == null)
                    return null;

                if (treeViewer.SelectedBone != null)
                    return treeViewer.SelectedBone;

                if (treeViewer.SelectedModel != null)
                    return treeViewer.SelectedModel;

                return null;
            }
        }

        public AdjustControls()
        {
            InitializeComponent();
            UpdateUI();

            timer.Tick += new EventHandler(OnTimer);
            timer.Enabled = false;
            timer.Interval = 33;
            timer.Start();

            stopwatch.Start();

            Configure(rotX, jogRX, JogRotateRange, false);
            Configure(rotY, jogRY, JogRotateRange, false);
            Configure(rotZ, jogRZ, JogRotateRange, false);
            Configure(tranX, jogTX, JogTranRange, false);
            Configure(tranY, jogTY, JogTranRange, false);
            Configure(tranZ, jogTZ, JogTranRange, false);
            Configure(scaleX, jogSX, JogScaleRange, true);
            Configure(scaleY, jogSY, JogScaleRange, true);
            Configure(scaleZ, jogSZ, JogScaleRange, true);
        }

        public void UpdateObserver()
        {
            UpdateUI();
        }


        private void StartTimer()
        {
            if (!timer.Enabled)
            {
                timer.Enabled = true;
                lastTime = stopwatch.ElapsedMilliseconds;
            }
        }

        private void MaybeStopTimer()
        {
            if (activeJogs.Count == 0)
                timer.Enabled = false;
        }


        /// <summary>
        /// This flag is set when the controls are beign updated
        /// to the current state. This prevents the control value change
        /// indications from conflicting with the update.
        /// </summary>
        private bool updating = false;

        private void UpdateUI()
        {
            // Prevent reentry when we change control values
            if (updating)
                return;

            updating = true;

            // Is any item selected at all?
            // If we have selected a mesh, we disable all controls except the Enable control
            IPlaceable placeable = Placeable;
            bool enable = placeable != null && treeViewer.SelectedMesh == null;
            bool selectedMesh = (treeViewer != null && treeViewer.SelectedMesh != null);

            rotX.Enabled = enable;
            jogRX.Enabled = enable;
            jogRX.Visible = enable;
            rotY.Enabled = enable;
            jogRY.Enabled = enable;
            jogRY.Visible = enable;
            rotZ.Enabled = enable;
            jogRZ.Enabled = enable;
            jogRZ.Visible = enable;
            checkDrawBones.Enabled = enable;
            
            

            tranX.Enabled = enable;
            jogTX.Enabled = enable;
            jogTX.Visible = enable;
            tranY.Enabled = enable;
            jogTY.Enabled = enable;
            jogTY.Visible = enable;
            tranZ.Enabled = enable;
            jogTZ.Enabled = enable;
            jogTZ.Visible = enable;

            scaleX.Enabled = enable;
            jogSX.Enabled = enable;
            jogSX.Visible = enable;
            
            if (uniformCheck.Checked)
            {
                scaleY.Enabled = false;
                jogSY.Enabled = false;
                jogSY.Visible = false;
                scaleZ.Enabled = false;
                jogSZ.Enabled = false;
                jogSZ.Visible = false;
            }
            else
            {
                scaleY.Enabled = enable;
                jogSY.Enabled = enable;
                jogSY.Visible = enable;
                scaleZ.Enabled = enable;
                jogSZ.Enabled = enable;
                jogSZ.Visible = enable;
            }

            uniformCheck.Enabled = enable;
            checkEnable.Enabled = enable || selectedMesh;
            buttonReset.Enabled = enable;
            buttonResetAll.Enabled = enable;

            if (enable)
            {
                rotX.Value = (decimal)MathHelper.ToDegrees(placeable.Rotation.X);
                rotY.Value = (decimal)MathHelper.ToDegrees(placeable.Rotation.Y);
                rotZ.Value = (decimal)MathHelper.ToDegrees(placeable.Rotation.Z);
                tranX.Value = (decimal)placeable.Translation.X;
                tranY.Value = (decimal)placeable.Translation.Y;
                tranZ.Value = (decimal)placeable.Translation.Z;
                scaleX.Value = (decimal)placeable.Scale.X;
                checkDrawBones.Checked = treeViewer.SelectedModel.DrawBones;

                if (uniformCheck.Checked)
                {
                    scaleY.Value = (decimal)placeable.Scale.X;
                    scaleZ.Value = (decimal)placeable.Scale.X;
                }
                else
                {
                    scaleY.Value = (decimal)placeable.Scale.Y;
                    scaleZ.Value = (decimal)placeable.Scale.Z;
                }

                checkEnable.Checked = placeable.Enabled;
            }
            else if (selectedMesh)
            {
                checkEnable.Checked = treeViewer.SelectedMesh.Enabled;
            }

            updating = false;
        }

        /// <summary>
        /// Configure a control with the appropriate limits and
        /// set the value changed function
        /// </summary>
        /// <param name="numUD"></param>
        private void Configure(NumericUpDown numUD, PictureBox picUD, double range, bool scaling)
        {
            numUD.ValueChanged += new System.EventHandler(this.OnValueChanged);
            if (picUD != null)
            {
                picUD.Click += new System.EventHandler(this.OnJogClicked);
                picUD.Tag = new Jog(numUD, range, scaling);
            }
        }

        private void OnValueChanged(object sender, EventArgs e)
        {
            if(updating)
                return;

            updating = true;

            // Get the values
            Vector3 translate = Vector3.Zero;
            Vector3 rotate = Vector3.Zero;
            Vector3 scale = new Vector3(1, 1, 1);
            
            rotate.X = MathHelper.ToRadians((float)rotX.Value);
            rotate.Y = MathHelper.ToRadians((float)rotY.Value);
            rotate.Z = MathHelper.ToRadians((float)rotZ.Value);
            translate.X = (float)tranX.Value;
            translate.Y = (float)tranY.Value;
            translate.Z = (float)tranZ.Value;

            scale.X = (float)scaleX.Value;
            if (uniformCheck.Checked)
            {
                scale.Y = scale.X;
                scale.Z = scale.X;

                scaleY.Value = (decimal)scale.X;
                scaleZ.Value = (decimal)scale.X;

            }
            else
            {
                scale.Y = (float)scaleY.Value;
                scale.Z = (float)scaleZ.Value;
            }
      
            if(Placeable != null)
            {
                IPlaceable placeable = Placeable;
                placeable.Translation = translate;
                placeable.Rotation = rotate;
                placeable.Scale = scale;
            }

            updating = false;
        }



        private void OnJogClicked(object sender, EventArgs e)
        {
            PictureBox jogPic = sender as PictureBox;
            if (jogPic == null)
                return;

            Jog jog = jogPic.Tag as Jog;
            if (jog == null)
                return;

            // If already active, don't start again
            if (jog.Active)
                return;

            jog.Start();
            activeJogs.AddLast(jog);

            StartTimer();
        }

        private void OnTimer(object sender, EventArgs args)
        {
            long newTime = stopwatch.ElapsedMilliseconds;
            double delta = (newTime - lastTime) * 0.001;
            lastTime = newTime;

            LinkedListNode<Jog> node = activeJogs.First;
            while (node != null)
            {
                LinkedListNode<Jog> next = node.Next;
                if (node.Value.Update(delta))
                {
                    // This jog is done
                    activeJogs.Remove(node);
                }

                node = next;
            }

            MaybeStopTimer();
        }

        /// <summary>
        /// An instance of the Jog class is created for every active jog
        /// </summary>
        class Jog
        {
            public const double JogDuration = 1.0;      // Seconds
            public const double JogRate = 2.0;          // Cycles per second

            public double JogRange = 0;                 // Range for jogging
            private bool scaling = false;                // Scaling is handled differently


            public Jog(NumericUpDown numUD, double range, bool scaling) 
            { 
                this.NumUD = numUD;
                this.JogRange = range;
                this.scaling = scaling;
            }

            public NumericUpDown NumUD { get; set; }

            public bool Active { get { return active; } }

            /// <summary>
            /// Value of the control before we jogged so we can restore it
            /// </summary>
            private float wasValue;

            public void Start()
            {
                time = 0;
                active = true;
                wasValue = (float)NumUD.Value;
            }

            public bool Update(double delta)
            {
                time += delta;
                double range = JogRange;
                if (scaling)
                    range = wasValue * JogRange;

                NumUD.Value = (decimal)(wasValue + Math.Sin(2 * Math.PI * time * JogRate) * range);

                if (time >= JogDuration)
                {
                    NumUD.Value = (decimal)wasValue;
                    active = false;
                    return true;
                }

                return false;
            }

            private double time = 0;
            private bool active = false;
        }

        LinkedList<Jog> activeJogs = new LinkedList<Jog>();


        private void buttonReset_Click(object sender, EventArgs e)
        {
            if (Placeable != null)
            {
                IPlaceable placeable = Placeable;
                placeable.Rotation = Vector3.Zero;
                placeable.Translation = Vector3.Zero;
                placeable.Scale = Vector3.One;
                placeable.Enabled = true;
                UpdateUI();
            }
        }

        private void buttonResetAll_Click(object sender, EventArgs e)
        {
            if (Placeable != null)
            {
                IPlaceable placeable = Placeable;
                placeable.ResetAll();
                UpdateUI();
            }
        }

        private void OnCheckChanged(object sender, EventArgs e)
        {
            if (uniformCheck.Checked)
            {
                // When we check, force all scales to match
                scaleY.Value = scaleX.Value;
                scaleZ.Value = scaleX.Value;
            }

            UpdateUI();
        }

        private void OnEnableChanged(object sender, EventArgs e)
        {
            if (treeViewer != null && treeViewer.SelectedMesh != null)
            {
                treeViewer.SelectedMesh.Enabled = checkEnable.Checked;
                UpdateUI();
            }
            else if (Placeable != null)
            {
                Placeable.SetEnabled(checkEnable.Checked);
                UpdateUI();
            }
        }

        private void OnDrawBonesChanged(object sender, EventArgs e)
        {
            if (treeViewer.SelectedModel != null)
            {
                treeViewer.SelectedModel.DrawBones = checkDrawBones.Checked;
                UpdateUI();
            }
        }
    }
}
